﻿using System;
using System.Diagnostics.Contracts;

namespace O1.Kernel.Storage.Transactions
{
    [ContractVerification(true)]
    public struct MultiVersion<T> : IEquatable<long>, IComparable<long>, IEquatable<MultiVersion<T>>
    {
        public readonly long Begin;

        public readonly long End;

        public readonly T Data;

        private const int A1 = 0;

        private const int B1 = 1;

        private const int C1 = 2;

        private const int D1 = 3;

        private const int A2 = 4;

        private const int A3 = 5;

        private const int B3 = 6;

        private const int C3 = 7;

        private const int D3 = 8;

        private const int C4 = 9;

        private const int C5 = 10;

        private const int D5 = 11;

        private static readonly int[] TxTable =
           new int[12] 
            {
                MultiVersionVisibility.None,
                MultiVersionVisibility.None,
                MultiVersionVisibility.None,
                MultiVersionVisibility.None,
                MultiVersionVisibility.ReadVisible,
                MultiVersionVisibility.ReadVisible,
                MultiVersionVisibility.ReadVisible,
                MultiVersionVisibility.ReadVisible,
                MultiVersionVisibility.ReadVisible,
                MultiVersionVisibility.ReadVisible,
                MultiVersionVisibility.ReadVisible,
                MultiVersionVisibility.None
            };

        public MultiVersion(long begin, long end, T data)
            : this()
        {
            this.End = end;
            this.Begin = begin;
            this.Data = data;
        }

        public MultiVersion(long begin, T data)
            : this()
        {
            this.End = MultiVersion.Infinity;
            this.Begin = begin;
            this.Data = data;
        }

        public long VersionId
        {
            get
            {
                return this.End != MultiVersion.Infinity ? this.End : this.Begin;
            }
        }

        public bool Pending
        {
            get
            {
                return this.VersionId > -1;
            }
        }

        public bool Expired
        {
            get
            {
                return this.End != MultiVersion.Infinity;
            }
        }

        public static bool operator ==(MultiVersion<T> value1, long value2)
        {
            return value1.Equals(value2);
        }

        public static bool operator !=(MultiVersion<T> value1, long value2)
        {
            return !value1.Equals(value2);
        }

        public static bool operator >(MultiVersion<T> value1, long value2)
        {
            return value1.CompareTo(value2) == 1;
        }

        public static bool operator <(MultiVersion<T> value1, long value2)
        {
            return value1.CompareTo(value2) == -1;
        }

        public static bool operator >=(MultiVersion<T> value1, long value2)
        {
            return value1.CompareTo(value2) > -1;
        }

        public static bool operator <=(MultiVersion<T> value1, long value2)
        {
            return value1.CompareTo(value2) < 1;
        }

        public static bool operator ==(long value1, MultiVersion<T> value2)
        {
            return value2.Equals(value1);
        }

        public static bool operator !=(long value1, MultiVersion<T> value2)
        {
            return !value2.Equals(value1);
        }

        public static bool operator >(long value1, MultiVersion<T> value2)
        {
            return value2.CompareTo(value1) == -1;
        }

        public static bool operator <(long value1, MultiVersion<T> value2)
        {
            return value2.CompareTo(value1) == 1;
        }

        public static bool operator >=(long value1, MultiVersion<T> value2)
        {
            return value2.CompareTo(value1) < 1;
        }

        public static bool operator <=(long value1, MultiVersion<T> value2)
        {
            return value2.CompareTo(value1) > -1;
        }

        public int ResolveVisibilities(long tx)
        {
            var isDelete = this.End != MultiVersion.Infinity;
            var beginId = this.Begin.AsPending();
            var txId = tx.AsPending();

            if (!this.Pending)
            {
                if (!isDelete)
                {
                    // 1B, 3B
                    return TxTable[txId < beginId ? B1 : B3];
                }

                // 1D, 3D, 5D
                if (txId < beginId)
                {
                    return TxTable[D1];
                }

                // 3D, 5D
                return TxTable[txId > this.End.AsPending() ? D5 : D3];
            }

            if (!isDelete)
            {
                // 1A, 2A, 3A
                // 1A
                if (txId < beginId)
                {
                    return TxTable[A1];
                }

                // 2A, 3A
                return TxTable[txId == beginId ? A2 : A3];
            }

            // 1C, 3C, 4C, 5C
            // 1C
            if (txId < beginId)
            {
                return TxTable[C1];
            }

            // 3C
            var deletedTx = this.End.AsPending();
            if (txId < deletedTx)
            {
                return TxTable[C3];
            }

            // 4C, 5C
            return TxTable[txId == deletedTx ? C4 : C5];
        }

        public int CompareTo(long other)
        {
            var thisValue = this.VersionId.AsPending();
            if (thisValue < other.AsPending())
            {
                return -1;
            }

            if (thisValue == other.AsPending())
            {
                return 0;
            }

            return 1;
        }

        public bool Equals(long other)
        {
            return other == this.VersionId;
        }

        public bool Equals(MultiVersion<T> other)
        {
            return other.Begin == this.Begin && other.End == this.End;
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            var t = obj.GetType();
            if (t == typeof(MultiVersion<T>))
            {
                return this.Equals((MultiVersion<T>)obj);
            }

            if (t == typeof(long))
            {
                return this.Equals((long)obj);
            }

            return false;
        }

        public override int GetHashCode()
        {
            return this.Begin.GetHashCode() ^ this.End.GetHashCode();
        }

        [ContractInvariantMethod]
        private static void ObjectInvariant()
        {
            Contract.Invariant(TxTable.Length == 12);
        }
    }
}